/**
 * \file
 *
 * Copyright (c) 2013-2018 Microchip Technology Inc. and its subsidiaries.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip
 * software and any derivatives exclusively with Microchip products.
 * It is your responsibility to comply with third party license terms applicable
 * to your use of third party software (including open source software) that
 * may accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
 * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
 * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
 * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
 * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
 * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
 * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT
 * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
 * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="https://www.microchip.com/support/">Microchip Support</a>
 */

#include "bootloader.h"

#include "asf.h"
#include "stdio_serial.h"
#include "conf_board.h"
#include "conf_clock.h"
#include "download.h"
#include "media.h"
#include "flash_efc.h"
#include "md5.h"


/* Dump written data after load */
#define DUMP_ENABLE 0

/** Cortex-M Code region start */
#define CM_CODE_START 0x00000080

/** Cortex-M Code region end */
#define CM_CODE_END 0x1FFFFF80

/** Cortex-M SRAM region end */
#define CM_SRAM_START 0x20000000

/** Cortex-M SRAM region end */
#define CM_SRAM_END 0x3FFFFF80

	
/* Default page count number */
#define DEFAULT_PAGE_COUNT (IFLASH1_SIZE / IFLASH1_PAGE_SIZE)

/* Default region count number */
#define DEFAULT_REGION_COUNT 1

/* Number of pages in a region */
#define IFLASH_NB_OF_PAGES IFLASH0_NB_OF_PAGES

/* Test page start address */
#define TEST_PAGE_ADDRESS (IFLASH1_ADDR + IFLASH1_SIZE - IFLASH1_PAGE_SIZE)

/* Flash start address */
#define IFLASH_ADDR IFLASH1_ADDR

/* Flash page size */
#define IFLASH_PAGE_SIZE IFLASH1_PAGE_SIZE

#define MEM_BLOCK_SIZE 256
#define MEM_BLANK_VALUE 0xFF

#define MEMORY_START_ADDRESS 0x0
#define APP_START_ADDRESS 0x0

/** IRQ priority for PIO (The lower the value, the greater the priority) */
#define IRQ_PRIOR_PIO 0

/** LED0 blink time, LED1 blink half this time, in ms */
#define BLINK_PERIOD 1000

#define MEMORY_PAGE_SIZE 256
#define MEMORY_PAGE_SIZE_32 (MEMORY_PAGE_SIZE / 4)


/** Turn LED on
 * \param led_pin The pin index that connected to LED
 */
#define _app_led_on(led_pin) \
	ioport_set_pin_level(led_pin, DEBUG_LED_PIN_ON_LEVEL)

/** Turn LED off
 * \param led_pin The pin index that connected to LED
 */
#define _app_led_off(led_pin) \
	ioport_set_pin_level(led_pin, !DEBUG_LED_PIN_ON_LEVEL)

/** Toggle LED
 * \param led_pin The pin index that connected to LED
 */
#define _app_led_toggle(led_pin) \
	ioport_toggle_pin_level(led_pin)

/** Global g_ul_ms_ticks in milliseconds since start of application */
volatile uint32_t g_ul_ms_ticks = 0;

/** Buffer to save one block of data */
static uint32_t app_mem_block_buf[MEM_BLOCK_SIZE / 4];


extern ota_file_header_t ota_file_header;

#ifdef __cplusplus
extern "C"
{
#endif

//------------------------------------------------------------------------------
// Functions
//------------------------------------------------------------------------------
/**
 *  \brief Handler for System Tick interrupt.
 *
 *  Process System Tick Event
 *  Increments the g_ul_ms_ticks counter.
 */
void SysTick_Handler(void)
{
	g_ul_ms_ticks++;
}

/**
 *  Configure Timer Counter 0 to generate an interrupt every 250ms.
 */
static void configure_tc(void)
{
	uint32_t ul_div;
	uint32_t ul_tcclks;
	uint32_t ul_sysclk = sysclk_get_cpu_hz();

	/* Configure PMC */
	pmc_enable_periph_clk(ID_TC0);

	/** Configure TC for a 4Hz frequency and trigger on RC compare. */
	tc_find_mck_divisor(4, ul_sysclk, &ul_div, &ul_tcclks, ul_sysclk);
	tc_init(TC0, 0, ul_tcclks | TC_CMR_CPCTRG);
	tc_write_rc(TC0, 0, (ul_sysclk / ul_div) / 4);

	/* Configure and enable interrupt on RC compare */
	NVIC_EnableIRQ((IRQn_Type)ID_TC0);
	tc_enable_interrupt(TC0, 0, TC_IER_CPCS);
	tc_start(TC0, 0);
}

/**
 * \brief Wait for the given number of milliseconds (using the g_ul_ms_ticks
 * generated by the SAM's microcontrollers's system tick).
 *
 * \param ul_dly_ticks  Delay to wait for, in milliseconds.
 */
void mdelay(uint32_t ul_dly_ticks)
{
	uint32_t ul_cur_ticks;

	ul_cur_ticks = g_ul_ms_ticks;
	while ((g_ul_ms_ticks - ul_cur_ticks) < ul_dly_ticks)
		;
}

void __segment_init(void) {}


/**
 * Get boot region information
 * \return 0 or 1.
 */
static uint32_t _app_boot_get(void)
{
	uint32_t gpmvm_status = flash_is_gpnvm_set(2);
	if (gpmvm_status == FLASH_RC_YES)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}


/**
 * Blink LED
 * \param delay_ms LED toggle delay time in ms
 * \param count Blink times
 */
static void _app_led_blink(uint32_t delay_ms, uint32_t count)
{
	uint32_t i;
	for (i = 0; i < count; i++)
	{
		_app_led_toggle(DEBUG_LED_PIN);
		// delay_ms(delay_ms);
		_app_led_toggle(DEBUG_LED_PIN);
		// delay_ms(delay_ms);
	}
}

/**
 * Indicate error
 * - ON for 1s
 * - Blink very fast 1s
 * - OFF
 */
static void _app_led_error(void)
{
	_app_led_on(DEBUG_LED_PIN);
	// delay_ms(1000);
	_app_led_blink(20, 25);
	_app_led_off(DEBUG_LED_PIN);
}

#if DUMP_ENABLE

	/**
	 * Dump memory block
	 *
	 * \param hint additional hints to display
	 * \param addr memory address
	 * \param size block size in bytes
	 * \param cut_off only first & last lines dummped if buffer size over 32 bytes
	 */
	static void _dump_data(char *hint, void *addr, uint32_t size, bool cut_off)
	{
		uint32_t line = 16;
		uint8_t *pu8 = (uint8_t *)addr;
		volatile uint32_t i;
		if (hint)
		{
			debug_print(hint);
		}

		debug_print("Dump data @ %x, size %d", addr, size);
		if (size < line)
		{
			line = size;
		}

		/* First line */
		debug_print("\r\n[%8x]", pu8);
		for (i = 0; i < line; i++)
		{
			debug_print(" %02x", pu8[i]);
		}
		/* Data body cut off */
		if (i < size)
		{
			if (cut_off)
			{
				debug_print("\r\n[........] ...");
				if (i + 16 < size)
				{
					i = (size % 16) ? (size &
									   0xFFFFFFFF0)
									: (size -
									   16);
				}
			}
		}

		/* Last lines */
		for (; i < size; i++)
		{
			if ((i % 16) == 0)
			{
				debug_print("\r\n[%8x]", &pu8[i]);
			}

			debug_print(" %02x", pu8[i]);
		}
		debug_print("\r\n");
	}

#endif

/**
 * Save memory block
 * \param addr Target address to save data
 * \param buf Source buffer
 * \param len Save size (must < MEM_BLOCK_SIZE)
 */
static uint32_t _app_save_block(void *addr, void *buf, uint32_t len)
{
	uint8_t *p_u8 = (uint8_t *)buf;
	if (len < MEM_BLOCK_SIZE)
	{
		/* Padding with MEM_BLANK_VALUE */
		for (; len < MEM_BLOCK_SIZE; len++)
		{
			p_u8[len] = MEM_BLANK_VALUE;
		}
	}

	/* Save one block */
	// memory_write(addr, buf);

	/* Toggle the led when app load. */
	_app_led_toggle(DEBUG_LED_PIN);

	return MEM_BLOCK_SIZE;
}

/**
 * Jump to CM vector table
 *
 * \param code_addr Application start address (vector table address)
 */
#if defined(__CC_ARM) /* Keil µVision 4 */
	__asm__ void jump_to_app(void *code_addr)
	{
		mov r1, r0 ldr r0, [ r1, #4 ] ldr sp, [r1] blx r0
	}

#elif defined(__ICCARM__) /* IAR Ewarm 5.41+ */
void jump_to_app(void *code_addr)
{
	UNUSED(code_addr);
	__asm(
		"mov     r1, r0        \n"
		"ldr     r0, [r1, #4]  \n"
		"ldr     sp, [r1]      \n"
		"blx     r0");
}

#elif defined(__GNUC__) /* GCC CS3 2009q3-68 */
void jump_to_app(void *code_addr)
{
	__asm__(
		"mov   r1, r0        \n"
		"ldr   r0, [r1, #4]  \n"
		"ldr   sp, [r1]      \n"
		"blx   r0");
}

#else /* General C, no stack reset */
void jump_to_app(void *code_addr)
{
	void (*pFct)(void) = NULL;
	/* Point on __main address located in the second word in vector table */
	pFct = (void (*)(void))(*(uint32_t *)((uint32_t)code_addr + 4));
	pFct();
}

#endif

/**
 * Execute the application binary
 *
 * \param addr Application start address.
 * \return If success, no return;
 *         1 - address alignment error;
 *         2 - address not executable.
 */
static uint8_t _app_exec(void *addr)
{
	uint32_t i;
	/* Check parameters */
	if ((uint32_t)addr & 0x7F)
	{
		return 1;
	}

	if ((uint32_t)addr > CM_SRAM_END)
	{
		return 2;
	}

	__disable_irq();
	/* Disable SysTick */
	SysTick->CTRL = 0;
	/* Disable IRQs & clear pending IRQs */
	for (i = 0; i < 8; i++)
	{
		NVIC->ICER[i] = 0xFFFFFFFF;
		NVIC->ICPR[i] = 0xFFFFFFFF;
	}

	/* Switch clock to slow RC */
	osc_enable(OSC_SLCK_32K_RC);
	osc_wait_ready(OSC_SLCK_32K_RC);
	pmc_switch_mck_to_sclk(SYSCLK_PRES_1);
	/* Switch clock to fast RC */
#if SAMG55
	osc_enable(OSC_MAINCK_24M_RC);
	osc_wait_ready(OSC_MAINCK_24M_RC);
#else
osc_enable(OSC_MAINCK_12M_RC);
osc_wait_ready(OSC_MAINCK_12M_RC);
#endif
	pmc_switch_mck_to_mainck(SYSCLK_PRES_1);

	/* Modify vector table location */
	__DSB();
	__ISB();
	SCB->VTOR = ((uint32_t)addr & SCB_VTOR_TBLOFF_Msk);
	__DSB();
	__ISB();
	__enable_irq();
	/* Jump to application */
	jump_to_app(addr);
	/* Never be here */
	return 0;
}

void user_timer_start(uint32_t *timer)
{
	*timer = g_ul_ms_ticks;
}

uint32_t user_timer_check(uint32_t *timer)
{
	return g_ul_ms_ticks - *timer;
}

void user_timer_stop(uint32_t *timer)
{
	*timer = 0;
}

/**
 * bootloader main entry
 */
int main(void)
{
	uint8_t boot_region = 0; /* real boot region at this time */

	void *app_addr = NULL;
	uint32_t app_size = 0;
	bool app_valid = false;
	int8_t ret;
	uint32_t unique_id[4];
	ota_info_t ota_info; 

	/* Initialize the SAM system */
	sysclk_init();
	board_init();

	/* Initialize the debug uart */
	debug_uart_init();
	media_usart_init();

	/* Initialize Flash service */
	ret = flash_init(FLASH_ACCESS_MODE_128, 4);
	if (ret != FLASH_RC_OK)
	{
		debug_print("-F- Initialization error %d\n\r", ret);
		return 0;
	}

	#if 0
		/* Read the unique ID */
		puts("-I- Reading 128 bits Unique Identifier\r");
		ret = flash_read_unique_id(unique_id, 4);
		if (ret != FLASH_RC_OK) {
			debug_print("-F- Read the Unique Identifier error %d\n\r", ret);
			return 0;
		}

		debug_print("-I- ID: 0x%08lu, 0x%08lu, 0x%08lu, 0x%08lu\n\r",unique_id[0], unique_id[1],unique_id[2], unique_id[3]);
	#endif
	
	/* Configure systick for 1 ms */
	//debug_print("configure system tick to get 1ms tick period.\r");
	if (SysTick_Config(sysclk_get_cpu_hz() / 1000))
	{
		debug_print("systick configuration error\r");
		while (1)
			;
	}

	debug_print("bootloader v1.0 startup\r\n");

	#if 0
	debug_print("app1 v1.0 startup\r\n");
	uint32_t count=0;
	while(1)
	{
		mdelay(1000);
		debug_print("app1 count:%d\r\n",count++);
	}
	#endif

	#if 0
	debug_print("app2 v1.0 startup\r\n");
	uint32_t count=0;
	while(1)
	{
		mdelay(1000);
		debug_print("app2 count:%d\r\n",count++);
	}
	#endif

	while (1)
	{
		ret = download_via_serial();
		if (ret > 0)
		{
			MD5_CTX_S md5_calc;
			uint8_t output[16];
			MD5Init(&md5_calc);
			MD5Update(&md5_calc, ota_file_header.img_load_address, ota_file_header.size);
			MD5Final(&md5_calc, output);

			if (memcmp(ota_file_header.sign, output, 16))
			{
				debug_print("md5 check err.\r\n");
			}
			else
			{
				debug_print("md5 check ok,%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\r\n",
							output[0],
							output[1],
							output[2],
							output[3],
							output[4],
							output[5],
							output[6],
							output[7],
							output[8],
							output[9],
							output[10],
							output[11],
							output[12],
							output[13],
							output[14],
							output[15]);
				// goto user app
				debug_print("user download finished,jump to user app and update boot addr\r\n");

                debug_print("-I- Unlocking page: 0x%08x\r\n", BOOTLOADER_INFO_ADDR);

                ret = flash_unlock(BOOTLOADER_INFO_ADDR, sizeof(ota_info_t), 0, 0);
                if (ret != FLASH_RC_OK)
                {
                   debug_print("-F- Unlock error %lu\n\r", ret);
                }
				else
				{
					/* Write page */
					debug_print("-I- Writing page\n\r");
					memcpy(ota_info.head,"INFO",4);
					ota_info.app_load_addr = ota_file_header.img_load_address;
					ret = flash_write(BOOTLOADER_INFO_ADDR, &ota_info , sizeof(ota_info_t), 1);
					if (ret != FLASH_RC_OK)
					{
						debug_print("-F- Flash programming error %lu\n\r", ret);
					}
					else
					{
						debug_print("-F- Flash programming OK.%x\n\r", ota_info.app_load_addr);
					}
			    }
				
				_app_exec(ota_file_header.img_load_address);
			}
		}
		else
		{
			memcpy(&ota_info,BOOTLOADER_INFO_ADDR,sizeof(ota_info_t));
			debug_print("serial download ret:%d,goto app1 ota_info.app_load_addr:%x\r\n", ret,ota_info.app_load_addr);
			_app_exec(ota_info.app_load_addr);
		}
	}
}

#ifdef __cplusplus
}
#endif

